home *** CD-ROM | disk | FTP | other *** search
/ Enter 2006 September / Enter 09 2006.iso / Internet / SpamExperts Home 1.1 / SpamExperts Home.exe / lib / spamexperts.modules / ZODB / FileStorage / FileStorage.pyc (.txt) < prev    next >
Encoding:
Python Compiled Bytecode  |  2006-07-14  |  48.6 KB  |  1,905 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyc (Python 2.4)
  3.  
  4. '''Storage implementation using a log written to a single file.
  5.  
  6. $Revision: 1.16 $
  7. '''
  8. import base64
  9. from cPickle import Pickler, Unpickler, loads
  10. import errno
  11. import os
  12. import sys
  13. import time
  14. import logging
  15. from types import StringType
  16. from struct import pack, unpack
  17. fsync = getattr(os, 'fsync', None)
  18. from ZODB import BaseStorage, ConflictResolution, POSException
  19. from ZODB.POSException import UndoError, POSKeyError, MultipleUndoErrors, VersionLockError
  20. from persistent.TimeStamp import TimeStamp
  21. from ZODB.lock_file import LockFile
  22. from ZODB.utils import p64, u64, cp, z64
  23. from ZODB.FileStorage.fspack import FileStoragePacker
  24. from ZODB.FileStorage.format import FileStorageFormatter, DataHeader, TxnHeader, DATA_HDR, DATA_HDR_LEN, TRANS_HDR, TRANS_HDR_LEN, CorruptedDataError
  25. from ZODB.loglevels import BLATHER
  26. from ZODB.fsIndex import fsIndex
  27. packed_version = 'FS21'
  28. logger = logging.getLogger('ZODB.FileStorage')
  29.  
  30. def panic(message, *data):
  31.     logger.critical(message, *data)
  32.     raise CorruptedTransactionError(message)
  33.  
  34.  
  35. class FileStorageError(POSException.StorageError):
  36.     pass
  37.  
  38.  
  39. class PackError(FileStorageError):
  40.     pass
  41.  
  42.  
  43. class FileStorageFormatError(FileStorageError):
  44.     '''Invalid file format
  45.  
  46.     The format of the given file is not valid.
  47.     '''
  48.     pass
  49.  
  50.  
  51. class CorruptedFileStorageError(FileStorageError, POSException.StorageSystemError):
  52.     '''Corrupted file storage.'''
  53.     pass
  54.  
  55.  
  56. class CorruptedTransactionError(CorruptedFileStorageError):
  57.     pass
  58.  
  59.  
  60. class FileStorageQuotaError(FileStorageError, POSException.StorageSystemError):
  61.     '''File storage quota exceeded.'''
  62.     pass
  63.  
  64.  
  65. class RedundantPackWarning(FileStorageError):
  66.     pass
  67.  
  68.  
  69. class TempFormatter(FileStorageFormatter):
  70.     '''Helper class used to read formatted FileStorage data.'''
  71.     
  72.     def __init__(self, afile):
  73.         self._file = afile
  74.  
  75.  
  76.  
  77. class FileStorage(BaseStorage.BaseStorage, ConflictResolution.ConflictResolvingStorage, FileStorageFormatter):
  78.     _pack_is_in_progress = False
  79.     _records_before_save = 10000
  80.     
  81.     def __init__(self, file_name, create = False, read_only = False, stop = None, quota = None):
  82.         if read_only:
  83.             self._is_read_only = True
  84.             if create:
  85.                 raise ValueError("can't create a read-only file")
  86.             
  87.         elif stop is not None:
  88.             raise ValueError('time-travel only supported in read-only mode')
  89.         
  90.         if stop is None:
  91.             stop = '\xff' * 8
  92.         
  93.         if not read_only:
  94.             self._lock_file = LockFile(file_name + '.lock')
  95.             self._tfile = open(file_name + '.tmp', 'w+b')
  96.             self._tfmt = TempFormatter(self._tfile)
  97.         else:
  98.             self._tfile = None
  99.         self._file_name = file_name
  100.         BaseStorage.BaseStorage.__init__(self, file_name)
  101.         (index, vindex, tindex, tvindex, oid2tid, toid2tid, toid2tid_delete) = self._newIndexes()
  102.         self._initIndex(index, vindex, tindex, tvindex, oid2tid, toid2tid, toid2tid_delete)
  103.         self._file = None
  104.         if not create:
  105.             
  106.             try:
  107.                 if not read_only or 'rb':
  108.                     pass
  109.                 self._file = open(file_name, 'r+b')
  110.             except IOError:
  111.                 exc = None
  112.                 if exc.errno == errno.EFBIG:
  113.                     raise 
  114.                 
  115.                 if exc.errno == errno.ENOENT:
  116.                     create = 1
  117.                 
  118.                 if os.path.exists(file_name):
  119.                     raise 
  120.                 else:
  121.                     create = 1
  122.             except:
  123.                 os.path.exists(file_name)
  124.             
  125.  
  126.         None<EXCEPTION MATCH>IOError
  127.         if self._file is None and create:
  128.             if os.path.exists(file_name):
  129.                 os.remove(file_name)
  130.             
  131.             self._file = open(file_name, 'w+b')
  132.             self._file.write(packed_version)
  133.         
  134.         r = self._restore_index()
  135.         if r is not None:
  136.             self._used_index = 1
  137.             (index, vindex, start, ltid) = r
  138.             self._initIndex(index, vindex, tindex, tvindex, oid2tid, toid2tid, toid2tid_delete)
  139.             (self._pos, self._oid, tid) = read_index(self._file, file_name, index, vindex, tindex, stop, ltid = ltid, start = start, read_only = read_only)
  140.         else:
  141.             self._used_index = 0
  142.             (self._pos, self._oid, tid) = read_index(self._file, file_name, index, vindex, tindex, stop, read_only = read_only)
  143.             self._save_index()
  144.         self._records_before_save = max(self._records_before_save, len(self._index))
  145.         self._ltid = tid
  146.         self._ts = tid = TimeStamp(tid)
  147.         t = time.time()
  148.         t = TimeStamp(*time.gmtime(t)[:5] + (t % 60,))
  149.         if tid > t:
  150.             seconds = tid.timeTime() - t.timeTime()
  151.             complainer = logger.warning
  152.             if seconds > 30 * 60:
  153.                 complainer = logger.critical
  154.             
  155.             complainer('%s Database records %d seconds in the future', file_name, seconds)
  156.         
  157.         self._quota = quota
  158.         self._oid2tid_nlookups = self._oid2tid_nhits = 0
  159.  
  160.     
  161.     def _initIndex(self, index, vindex, tindex, tvindex, oid2tid, toid2tid, toid2tid_delete):
  162.         self._index = index
  163.         self._vindex = vindex
  164.         self._tindex = tindex
  165.         self._tvindex = tvindex
  166.         self._index_get = index.get
  167.         self._vindex_get = vindex.get
  168.         self._oid2tid = oid2tid
  169.         self._toid2tid = toid2tid
  170.         self._toid2tid_delete = toid2tid_delete
  171.  
  172.     
  173.     def __len__(self):
  174.         return len(self._index)
  175.  
  176.     
  177.     def _newIndexes(self):
  178.         return (fsIndex(), { }, { }, { }, { }, { }, { })
  179.  
  180.     _saved = 0
  181.     
  182.     def _save_index(self):
  183.         '''Write the database index to a file to support quick startup.'''
  184.         if self._is_read_only:
  185.             return None
  186.         
  187.         index_name = self.__name__ + '.index'
  188.         tmp_name = index_name + '.index_tmp'
  189.         f = open(tmp_name, 'wb')
  190.         p = Pickler(f, 1)
  191.         info = {
  192.             'index': self._index,
  193.             'pos': self._pos,
  194.             'oid': self._oid,
  195.             'vindex': self._vindex }
  196.         p.dump(info)
  197.         f.flush()
  198.         f.close()
  199.         
  200.         try:
  201.             
  202.             try:
  203.                 os.remove(index_name)
  204.             except OSError:
  205.                 pass
  206.  
  207.             os.rename(tmp_name, index_name)
  208.         except:
  209.             pass
  210.  
  211.         self._saved += 1
  212.  
  213.     
  214.     def _clear_index(self):
  215.         index_name = self.__name__ + '.index'
  216.         if os.path.exists(index_name):
  217.             
  218.             try:
  219.                 os.remove(index_name)
  220.             except OSError:
  221.                 pass
  222.             except:
  223.                 None<EXCEPTION MATCH>OSError
  224.             
  225.  
  226.         None<EXCEPTION MATCH>OSError
  227.  
  228.     
  229.     def _sane(self, index, pos):
  230.         '''Sanity check saved index data by reading the last undone trans
  231.  
  232.         Basically, we read the last not undone transaction and
  233.         check to see that the included records are consistent
  234.         with the index.  Any invalid record records or inconsistent
  235.         object positions cause zero to be returned.
  236.         '''
  237.         r = self._check_sanity(index, pos)
  238.         if not r:
  239.             logger.warning('Ignoring index for %s', self._file_name)
  240.         
  241.         return r
  242.  
  243.     
  244.     def _check_sanity(self, index, pos):
  245.         if pos < 100:
  246.             return 0
  247.         
  248.         self._file.seek(0, 2)
  249.         if self._file.tell() < pos:
  250.             return 0
  251.         
  252.         ltid = None
  253.         max_checked = 5
  254.         checked = 0
  255.         while checked < max_checked:
  256.             self._file.seek(pos - 8)
  257.             rstl = self._file.read(8)
  258.             tl = u64(rstl)
  259.             pos = pos - tl - 8
  260.             if pos < 4:
  261.                 return 0
  262.             
  263.             h = self._read_txn_header(pos)
  264.             if not ltid:
  265.                 ltid = h.tid
  266.             
  267.             if h.tlen != tl:
  268.                 return 0
  269.             
  270.             if h.status == 'u':
  271.                 continue
  272.             
  273.             if h.status not in ' p':
  274.                 return 0
  275.             
  276.             if tl < h.headerlen():
  277.                 return 0
  278.             
  279.             tend = pos + tl
  280.             opos = pos + h.headerlen()
  281.             if opos == tend:
  282.                 continue
  283.             
  284.             while opos < tend and checked < max_checked:
  285.                 h = self._read_data_header(opos)
  286.                 if opos + h.recordlen() > tend or h.tloc != pos:
  287.                     return 0
  288.                 
  289.                 if index.get(h.oid, 0) != opos:
  290.                     return 0
  291.                 
  292.                 checked += 1
  293.                 opos = opos + h.recordlen()
  294.             return ltid
  295.  
  296.     
  297.     def _restore_index(self):
  298.         '''Load database index to support quick startup.'''
  299.         file_name = self.__name__
  300.         index_name = file_name + '.index'
  301.         
  302.         try:
  303.             f = open(index_name, 'rb')
  304.         except:
  305.             return None
  306.  
  307.         p = Unpickler(f)
  308.         
  309.         try:
  310.             info = p.load()
  311.         except:
  312.             (exc, err) = sys.exc_info()[:2]
  313.             logger.warning('Failed to load database index: %s: %s', exc, err)
  314.             return None
  315.  
  316.         index = info.get('index')
  317.         pos = info.get('pos')
  318.         vindex = info.get('vindex')
  319.         if index is None and pos is None or vindex is None:
  320.             return None
  321.         
  322.         pos = long(pos)
  323.         if (isinstance(index, dict) or isinstance(index, fsIndex)) and isinstance(index._data, dict):
  324.             newindex = fsIndex()
  325.             newindex.update(index)
  326.             index = newindex
  327.             if not self._is_read_only:
  328.                 f = open(index_name, 'wb')
  329.                 p = Pickler(f, 1)
  330.                 info['index'] = index
  331.                 p.dump(info)
  332.                 f.close()
  333.                 return self._restore_index()
  334.             
  335.         
  336.         tid = self._sane(index, pos)
  337.         if not tid:
  338.             return None
  339.         
  340.         return (index, vindex, pos, tid)
  341.  
  342.     
  343.     def close(self):
  344.         self._file.close()
  345.         if hasattr(self, '_lock_file'):
  346.             self._lock_file.close()
  347.         
  348.         if self._tfile:
  349.             self._tfile.close()
  350.         
  351.         
  352.         try:
  353.             self._save_index()
  354.         except:
  355.             logger.error('Error saving index on close()', exc_info = True)
  356.  
  357.  
  358.     
  359.     def _get_cached_tid(self, oid):
  360.         self._oid2tid_nlookups += 1
  361.         result = self._oid2tid.get(oid)
  362.         if self._oid2tid_nlookups & 8191 == 0:
  363.             logger.log(BLATHER, '_oid2tid size %s lookups %s hits %s rate %.1f%%', len(self._oid2tid), self._oid2tid_nlookups, self._oid2tid_nhits, 100.0 * self._oid2tid_nhits / self._oid2tid_nlookups)
  364.         
  365.         return result
  366.  
  367.     
  368.     def abortVersion(self, src, transaction):
  369.         return self.commitVersion(src, '', transaction, abort = True)
  370.  
  371.     
  372.     def commitVersion(self, src, dest, transaction, abort = False):
  373.         if self._is_read_only:
  374.             raise POSException.ReadOnlyError()
  375.         
  376.         if not src and isinstance(src, StringType) and isinstance(dest, StringType):
  377.             raise POSException.VersionCommitError('Invalid source version')
  378.         
  379.         if src == dest:
  380.             raise POSException.VersionCommitError("Can't commit to same version: %s" % repr(src))
  381.         
  382.         if dest and abort:
  383.             raise POSException.VersionCommitError("Internal error, can't abort to a version")
  384.         
  385.         if transaction is not self._transaction:
  386.             raise POSException.StorageTransactionError(self, transaction)
  387.         
  388.         self._lock_acquire()
  389.         
  390.         try:
  391.             return self._commitVersion(src, dest, transaction, abort)
  392.         finally:
  393.             self._lock_release()
  394.  
  395.  
  396.     
  397.     def _commitVersion(self, src, dest, transaction, abort = False):
  398.         srcpos = self._vindex_get(src, 0)
  399.         spos = p64(srcpos)
  400.         middle = pack('>8sH8s', p64(self._pos), len(dest), z64)
  401.         if dest:
  402.             sd = p64(self._vindex_get(dest, 0))
  403.             heredelta = 66 + len(dest)
  404.         else:
  405.             sd = ''
  406.             heredelta = 50
  407.         here = self._pos + self._tfile.tell() + self._thl
  408.         oids = []
  409.         current_oids = { }
  410.         while srcpos:
  411.             h = self._read_data_header(srcpos)
  412.             if self._index.get(h.oid) == srcpos:
  413.                 self._tindex[h.oid] = here
  414.                 oids.append(h.oid)
  415.                 self._tfile.write(h.oid + self._tid + spos + middle)
  416.                 if dest:
  417.                     self._tvindex[dest] = here
  418.                     self._tfile.write(p64(h.pnv) + sd + dest)
  419.                     sd = p64(here)
  420.                 
  421.                 if not abort or p64(h.pnv):
  422.                     pass
  423.                 self._tfile.write(spos)
  424.                 here += heredelta
  425.                 current_oids[h.oid] = 1
  426.             elif not current_oids.has_key(h.oid):
  427.                 break
  428.             
  429.             srcpos = h.vprev
  430.             spos = p64(srcpos)
  431.         self._toid2tid_delete.update(current_oids)
  432.         return (self._tid, oids)
  433.  
  434.     
  435.     def getSize(self):
  436.         return self._pos
  437.  
  438.     
  439.     def _lookup_pos(self, oid):
  440.         
  441.         try:
  442.             return self._index[oid]
  443.         except KeyError:
  444.             raise POSKeyError(oid)
  445.         except TypeError:
  446.             raise TypeError('invalid oid %r' % (oid,))
  447.  
  448.  
  449.     
  450.     def loadEx(self, oid, version):
  451.         self._lock_acquire()
  452.         
  453.         try:
  454.             pos = self._lookup_pos(oid)
  455.             h = self._read_data_header(pos, oid)
  456.             if h.version and h.version != version:
  457.                 data = self._loadBack_impl(oid, h.pnv)[0]
  458.                 return (data, h.tid, '')
  459.             
  460.             if h.plen:
  461.                 data = self._file.read(h.plen)
  462.                 return (data, h.tid, h.version)
  463.             else:
  464.                 data = self._loadBack_impl(oid, h.back)[0]
  465.                 return (data, h.tid, h.version)
  466.         finally:
  467.             self._lock_release()
  468.  
  469.  
  470.     
  471.     def load(self, oid, version):
  472.         '''Return pickle data and serial number.'''
  473.         self._lock_acquire()
  474.         
  475.         try:
  476.             pos = self._lookup_pos(oid)
  477.             h = self._read_data_header(pos, oid)
  478.             if h.version and h.version != version:
  479.                 data = self._loadBack_impl(oid, h.pnv)[0]
  480.                 return (data, h.tid)
  481.             
  482.             if h.plen:
  483.                 data = self._file.read(h.plen)
  484.                 return (data, h.tid)
  485.             else:
  486.                 data = self._loadBack_impl(oid, h.back)[0]
  487.                 return (data, h.tid)
  488.         finally:
  489.             self._lock_release()
  490.  
  491.  
  492.     
  493.     def loadSerial(self, oid, serial):
  494.         self._lock_acquire()
  495.         
  496.         try:
  497.             pos = self._lookup_pos(oid)
  498.             while None:
  499.                 h = self._read_data_header(pos, oid)
  500.                 if h.tid == serial:
  501.                     break
  502.                 
  503.                 pos = h.prev
  504.                 if not pos:
  505.                     raise POSKeyError(oid)
  506.                     continue
  507.             if h.version:
  508.                 return self._loadBack_impl(oid, h.pnv)[0]
  509.             
  510.             if h.plen:
  511.                 return self._file.read(h.plen)
  512.             else:
  513.                 return self._loadBack_impl(oid, h.back)[0]
  514.         finally:
  515.             self._lock_release()
  516.  
  517.  
  518.     
  519.     def loadBefore(self, oid, tid):
  520.         self._lock_acquire()
  521.         
  522.         try:
  523.             pos = self._lookup_pos(oid)
  524.             end_tid = None
  525.             while True:
  526.                 h = self._read_data_header(pos, oid)
  527.                 if h.version:
  528.                     if not h.pnv:
  529.                         return None
  530.                     
  531.                     pos = h.pnv
  532.                     continue
  533.                 
  534.                 if h.tid < tid:
  535.                     break
  536.                 
  537.                 pos = h.prev
  538.                 end_tid = h.tid
  539.                 if not pos:
  540.                     return None
  541.                     continue
  542.             if h.back:
  543.                 (data, _, _, _) = self._loadBack_impl(oid, h.back)
  544.                 return (data, h.tid, end_tid)
  545.             else:
  546.                 return (self._file.read(h.plen), h.tid, end_tid)
  547.         finally:
  548.             self._lock_release()
  549.  
  550.  
  551.     
  552.     def modifiedInVersion(self, oid):
  553.         self._lock_acquire()
  554.         
  555.         try:
  556.             pos = self._lookup_pos(oid)
  557.             h = self._read_data_header(pos, oid)
  558.             return h.version
  559.         finally:
  560.             self._lock_release()
  561.  
  562.  
  563.     
  564.     def store(self, oid, serial, data, version, transaction):
  565.         if self._is_read_only:
  566.             raise POSException.ReadOnlyError()
  567.         
  568.         if transaction is not self._transaction:
  569.             raise POSException.StorageTransactionError(self, transaction)
  570.         
  571.         self._lock_acquire()
  572.         
  573.         try:
  574.             if oid > self._oid:
  575.                 self.set_max_oid(oid)
  576.             
  577.             old = self._index_get(oid, 0)
  578.             cached_tid = None
  579.             pnv = None
  580.             if old:
  581.                 cached_tid = self._get_cached_tid(oid)
  582.                 if cached_tid is None:
  583.                     h = self._read_data_header(old, oid)
  584.                     if h.version:
  585.                         if h.version != version:
  586.                             raise VersionLockError(oid, h.version)
  587.                         
  588.                         pnv = h.pnv
  589.                     
  590.                     cached_tid = h.tid
  591.                 
  592.                 if serial != cached_tid:
  593.                     rdata = self.tryToResolveConflict(oid, cached_tid, serial, data)
  594.                     if rdata is None:
  595.                         raise POSException.ConflictError(oid = oid, serials = (cached_tid, serial), data = data)
  596.                     else:
  597.                         data = rdata
  598.                 
  599.             
  600.             pos = self._pos
  601.             here = pos + self._tfile.tell() + self._thl
  602.             self._tindex[oid] = here
  603.             new = DataHeader(oid, self._tid, old, pos, len(version), len(data))
  604.             if version:
  605.                 if not self._tvindex.get(version, 0):
  606.                     pass
  607.                 pv = self._vindex.get(version, 0)
  608.                 if pnv is None:
  609.                     pnv = old
  610.                 
  611.                 new.setVersion(version, pnv, pv)
  612.                 self._tvindex[version] = here
  613.                 self._toid2tid_delete[oid] = 1
  614.             else:
  615.                 self._toid2tid[oid] = self._tid
  616.             self._tfile.write(new.asString())
  617.             self._tfile.write(data)
  618.             if self._quota is not None and here > self._quota:
  619.                 raise FileStorageQuotaError('The storage quota has been exceeded.')
  620.             
  621.             if old and serial != cached_tid:
  622.                 return ConflictResolution.ResolvedSerial
  623.             else:
  624.                 return self._tid
  625.         finally:
  626.             self._lock_release()
  627.  
  628.  
  629.     
  630.     def _data_find(self, tpos, oid, data):
  631.         self._file.seek(tpos)
  632.         h = self._file.read(TRANS_HDR_LEN)
  633.         (tid, tl, status, ul, dl, el) = unpack(TRANS_HDR, h)
  634.         self._file.read(ul + dl + el)
  635.         tend = tpos + tl + 8
  636.         pos = self._file.tell()
  637.         while pos < tend:
  638.             h = self._read_data_header(pos)
  639.             if h.oid == oid:
  640.                 if h.plen == 0:
  641.                     return pos
  642.                 
  643.                 if h.plen != len(data):
  644.                     logger.error('Mismatch between data and backpointer at %d', pos)
  645.                     return 0
  646.                 
  647.                 _data = self._file.read(h.plen)
  648.                 if data != _data:
  649.                     return 0
  650.                 
  651.                 return pos
  652.             
  653.             pos += h.recordlen()
  654.             self._file.seek(pos)
  655.         return 0
  656.  
  657.     
  658.     def restore(self, oid, serial, data, version, prev_txn, transaction):
  659.         if self._is_read_only:
  660.             raise POSException.ReadOnlyError()
  661.         
  662.         if transaction is not self._transaction:
  663.             raise POSException.StorageTransactionError(self, transaction)
  664.         
  665.         self._lock_acquire()
  666.         
  667.         try:
  668.             if oid > self._oid:
  669.                 self.set_max_oid(oid)
  670.             
  671.             prev_pos = 0
  672.             if prev_txn is not None:
  673.                 prev_txn_pos = self._txn_find(prev_txn, 0)
  674.                 if prev_txn_pos:
  675.                     prev_pos = self._data_find(prev_txn_pos, oid, data)
  676.                 
  677.             
  678.             old = self._index_get(oid, 0)
  679.             here = self._pos + self._tfile.tell() + self._thl
  680.             self._tindex[oid] = here
  681.             if prev_pos:
  682.                 data = None
  683.             
  684.             if data is None:
  685.                 dlen = 0
  686.             else:
  687.                 dlen = len(data)
  688.             new = DataHeader(oid, serial, old, self._pos, len(version), dlen)
  689.             if version:
  690.                 if not self._restore_pnv(oid, old, version, prev_pos):
  691.                     pass
  692.                 pnv = old
  693.                 vprev = self._tvindex.get(version, 0)
  694.                 if not vprev:
  695.                     vprev = self._vindex.get(version, 0)
  696.                 
  697.                 new.setVersion(version, pnv, vprev)
  698.                 self._tvindex[version] = here
  699.                 self._toid2tid_delete[oid] = 1
  700.             else:
  701.                 self._toid2tid[oid] = serial
  702.             self._tfile.write(new.asString())
  703.             if data is None:
  704.                 if prev_pos:
  705.                     self._tfile.write(p64(prev_pos))
  706.                 else:
  707.                     self._tfile.write(z64)
  708.             else:
  709.                 self._tfile.write(data)
  710.         finally:
  711.             self._lock_release()
  712.  
  713.  
  714.     
  715.     def _restore_pnv(self, oid, prev, version, bp):
  716.         if not prev:
  717.             return None
  718.         
  719.         h = self._read_data_header(prev, oid)
  720.         if h.version:
  721.             return h.pnv
  722.         
  723.         if h.back:
  724.             h2 = self._read_data_header(h.back, oid)
  725.             if h2.version:
  726.                 return h2.pnv
  727.             
  728.         
  729.  
  730.     
  731.     def supportsUndo(self):
  732.         return 1
  733.  
  734.     
  735.     def supportsVersions(self):
  736.         return 1
  737.  
  738.     
  739.     def _clear_temp(self):
  740.         self._tindex.clear()
  741.         self._tvindex.clear()
  742.         self._toid2tid.clear()
  743.         self._toid2tid_delete.clear()
  744.         if self._tfile is not None:
  745.             self._tfile.seek(0)
  746.         
  747.  
  748.     
  749.     def _begin(self, tid, u, d, e):
  750.         self._nextpos = 0
  751.         self._thl = TRANS_HDR_LEN + len(u) + len(d) + len(e)
  752.         if self._thl > 65535:
  753.             if len(u) > 65535:
  754.                 raise FileStorageError('user name too long')
  755.             
  756.             if len(d) > 65535:
  757.                 raise FileStorageError('description too long')
  758.             
  759.             if len(e) > 65535:
  760.                 raise FileStorageError('too much extension data')
  761.             
  762.         
  763.  
  764.     
  765.     def tpc_vote(self, transaction):
  766.         self._lock_acquire()
  767.         
  768.         try:
  769.             if transaction is not self._transaction:
  770.                 return None
  771.             
  772.             dlen = self._tfile.tell()
  773.             if not dlen:
  774.                 return None
  775.             
  776.             self._tfile.seek(0)
  777.             (user, descr, ext) = self._ude
  778.             self._file.seek(self._pos)
  779.             tl = self._thl + dlen
  780.             
  781.             try:
  782.                 h = TxnHeader(self._tid, tl, 'c', len(user), len(descr), len(ext))
  783.                 h.user = user
  784.                 h.descr = descr
  785.                 h.ext = ext
  786.                 self._file.write(h.asString())
  787.                 cp(self._tfile, self._file, dlen)
  788.                 self._file.write(p64(tl))
  789.                 self._file.flush()
  790.             except:
  791.                 self._file.truncate(self._pos)
  792.                 raise 
  793.  
  794.             self._nextpos = self._pos + tl + 8
  795.         finally:
  796.             self._lock_release()
  797.  
  798.  
  799.     _records_written = 0
  800.     
  801.     def _finish(self, tid, u, d, e):
  802.         nextpos = self._nextpos
  803.         self._ltid = tid
  804.  
  805.     
  806.     def _abort(self):
  807.         if self._nextpos:
  808.             self._file.truncate(self._pos)
  809.             self._nextpos = 0
  810.         
  811.  
  812.     
  813.     def supportsTransactionalUndo(self):
  814.         return 1
  815.  
  816.     
  817.     def _undoDataInfo(self, oid, pos, tpos):
  818.         '''Return the tid, data pointer, data, and version for the oid
  819.         record at pos'''
  820.         if tpos:
  821.             pos = tpos - self._pos - self._thl
  822.             tpos = self._tfile.tell()
  823.             h = self._tfmt._read_data_header(pos, oid)
  824.             afile = self._tfile
  825.         else:
  826.             h = self._read_data_header(pos, oid)
  827.             afile = self._file
  828.         if h.oid != oid:
  829.             raise UndoError('Invalid undo transaction id', oid)
  830.         
  831.         if h.plen:
  832.             data = afile.read(h.plen)
  833.         else:
  834.             data = ''
  835.             pos = h.back
  836.         if tpos:
  837.             self._tfile.seek(tpos)
  838.         
  839.         return (h.tid, pos, data, h.version)
  840.  
  841.     
  842.     def getTid(self, oid):
  843.         self._lock_acquire()
  844.         
  845.         try:
  846.             result = self._get_cached_tid(oid)
  847.             if result is None:
  848.                 pos = self._lookup_pos(oid)
  849.                 result = self._getTid(oid, pos)
  850.             
  851.             return result
  852.         finally:
  853.             self._lock_release()
  854.  
  855.  
  856.     
  857.     def _getTid(self, oid, pos):
  858.         self._file.seek(pos)
  859.         h = self._file.read(16)
  860.         if not oid == h[:8]:
  861.             raise AssertionError
  862.         return h[8:]
  863.  
  864.     
  865.     def _getVersion(self, oid, pos):
  866.         h = self._read_data_header(pos, oid)
  867.         if h.version:
  868.             return (h.version, h.pnv)
  869.         else:
  870.             return ('', None)
  871.  
  872.     
  873.     def _transactionalUndoRecord(self, oid, pos, tid, pre, version):
  874.         '''Get the indo information for a data record
  875.  
  876.         Return a 5-tuple consisting of a pickle, data pointer,
  877.         version, packed non-version data pointer, and current
  878.         position.  If the pickle is true, then the data pointer must
  879.         be 0, but the pickle can be empty *and* the pointer 0.
  880.         '''
  881.         copy = 1
  882.         tpos = self._tindex.get(oid, 0)
  883.         ipos = self._index.get(oid, 0)
  884.         if not tpos:
  885.             pass
  886.         tipos = ipos
  887.         if tipos != pos:
  888.             (ctid, cdataptr, cdata, cver) = self._undoDataInfo(oid, ipos, tpos)
  889.             if cver != version:
  890.                 raise UndoError('Current and undone versions differ', oid)
  891.             
  892.             if cdataptr != pos:
  893.                 
  894.                 try:
  895.                     if cdataptr == tipos or self._loadBackPOS(oid, pos) != self._loadBackPOS(oid, cdataptr):
  896.                         if pre and not tpos:
  897.                             copy = 0
  898.                         else:
  899.                             raise UndoError('no previous record', oid)
  900.                 except KeyError:
  901.                     raise UndoError('_loadBack() failed', oid)
  902.                 except:
  903.                     None<EXCEPTION MATCH>KeyError
  904.                 
  905.  
  906.             None<EXCEPTION MATCH>KeyError
  907.         
  908.         if not pre:
  909.             return ('', 0, '', '', ipos)
  910.         
  911.         (version, snv) = self._getVersion(oid, pre)
  912.         if copy:
  913.             return ('', pre, version, snv, ipos)
  914.         
  915.         
  916.         try:
  917.             bdata = self._loadBack_impl(oid, pre)[0]
  918.         except KeyError:
  919.             raise UndoError('_loadBack() failed for %s', oid)
  920.  
  921.         data = self.tryToResolveConflict(oid, ctid, tid, bdata, cdata)
  922.         if data:
  923.             return (data, 0, version, snv, ipos)
  924.         
  925.         raise UndoError('Some data were modified by a later transaction', oid)
  926.  
  927.     
  928.     def undoLog(self, first = 0, last = -20, filter = None):
  929.         if last < 0:
  930.             last = first - last
  931.         
  932.         self._lock_acquire()
  933.         
  934.         try:
  935.             if self._pack_is_in_progress:
  936.                 raise UndoError('Undo is currently disabled for database maintenance.<p>')
  937.             
  938.             us = UndoSearch(self._file, self._pos, first, last, filter)
  939.             while not us.finished():
  940.                 for i in range(20):
  941.                     if us.finished():
  942.                         break
  943.                     
  944.                     us.search()
  945.                 
  946.                 self._lock_release()
  947.                 self._lock_acquire()
  948.             return us.results
  949.         finally:
  950.             self._lock_release()
  951.  
  952.  
  953.     
  954.     def undo(self, transaction_id, transaction):
  955.         '''Undo a transaction, given by transaction_id.
  956.  
  957.         Do so by writing new data that reverses the action taken by
  958.         the transaction.
  959.  
  960.         Usually, we can get by with just copying a data pointer, by
  961.         writing a file position rather than a pickle. Sometimes, we
  962.         may do conflict resolution, in which case we actually copy
  963.         new data that results from resolution.
  964.         '''
  965.         if self._is_read_only:
  966.             raise POSException.ReadOnlyError()
  967.         
  968.         if transaction is not self._transaction:
  969.             raise POSException.StorageTransactionError(self, transaction)
  970.         
  971.         self._lock_acquire()
  972.         
  973.         try:
  974.             return self._txn_undo(transaction_id)
  975.         finally:
  976.             self._lock_release()
  977.  
  978.  
  979.     
  980.     def _txn_undo(self, transaction_id):
  981.         tid = base64.decodestring(transaction_id + '\n')
  982.         if not len(tid) == 8:
  983.             raise AssertionError
  984.         tpos = self._txn_find(tid, 1)
  985.         tindex = self._txn_undo_write(tpos)
  986.         self._tindex.update(tindex)
  987.         self._toid2tid_delete.update(tindex)
  988.         return (self._tid, tindex.keys())
  989.  
  990.     
  991.     def _txn_find(self, tid, stop_at_pack):
  992.         pos = self._pos
  993.         while pos > 39:
  994.             self._file.seek(pos - 8)
  995.             pos = pos - u64(self._file.read(8)) - 8
  996.             self._file.seek(pos)
  997.             h = self._file.read(TRANS_HDR_LEN)
  998.             _tid = h[:8]
  999.             if _tid == tid:
  1000.                 return pos
  1001.             
  1002.             if stop_at_pack:
  1003.                 if h[16] == 'p':
  1004.                     break
  1005.                 
  1006.             h[16] == 'p'
  1007.         raise UndoError('Invalid transaction id')
  1008.  
  1009.     
  1010.     def _txn_undo_write(self, tpos):
  1011.         otloc = self._pos
  1012.         here = self._pos + self._tfile.tell() + self._thl
  1013.         base = here - self._tfile.tell()
  1014.         th = self._read_txn_header(tpos)
  1015.         if th.status != ' ':
  1016.             raise UndoError('non-undoable transaction')
  1017.         
  1018.         tend = tpos + th.tlen
  1019.         pos = tpos + th.headerlen()
  1020.         tindex = { }
  1021.         failures = { }
  1022.         while pos < tend:
  1023.             h = self._read_data_header(pos)
  1024.             if h.oid in failures:
  1025.                 del failures[h.oid]
  1026.             
  1027.             if not base + self._tfile.tell() == here:
  1028.                 raise AssertionError, (here, base, self._tfile.tell())
  1029.             
  1030.             try:
  1031.                 (p, prev, v, snv, ipos) = self._transactionalUndoRecord(h.oid, pos, h.tid, h.prev, h.version)
  1032.             except UndoError:
  1033.                 v = None
  1034.                 failures[h.oid] = v
  1035.  
  1036.             new = DataHeader(h.oid, self._tid, ipos, otloc, len(v), len(p))
  1037.             if v:
  1038.                 if not self._tvindex.get(v, 0):
  1039.                     pass
  1040.                 vprev = self._vindex.get(v, 0)
  1041.                 new.setVersion(v, snv, vprev)
  1042.                 self._tvindex[v] = here
  1043.             
  1044.             if not self._tfile.tell() == here - base:
  1045.                 raise AssertionError, (here, base, self._tfile.tell())
  1046.             self._tfile.write(new.asString())
  1047.             if p:
  1048.                 self._tfile.write(p)
  1049.             else:
  1050.                 self._tfile.write(p64(prev))
  1051.             tindex[h.oid] = here
  1052.             here += new.recordlen()
  1053.             pos += h.recordlen()
  1054.             if pos > tend:
  1055.                 raise UndoError('non-undoable transaction')
  1056.                 continue
  1057.         if failures:
  1058.             raise MultipleUndoErrors(failures.items())
  1059.         
  1060.         return tindex
  1061.  
  1062.     
  1063.     def versionEmpty(self, version):
  1064.         if not version:
  1065.             raise POSException.VersionError('The version must be an non-empty string')
  1066.         
  1067.         self._lock_acquire()
  1068.         
  1069.         try:
  1070.             index = self._index
  1071.             file = self._file
  1072.             seek = file.seek
  1073.             read = file.read
  1074.             srcpos = self._vindex_get(version, 0)
  1075.             t = None
  1076.             tstatus = None
  1077.             while srcpos:
  1078.                 seek(srcpos)
  1079.                 oid = read(8)
  1080.                 if index[oid] == srcpos:
  1081.                     return 0
  1082.                 
  1083.                 h = read(50)
  1084.                 tloc = h[16:24]
  1085.                 if t != tloc:
  1086.                     t = tloc
  1087.                     seek(u64(t) + 16)
  1088.                     tstatus = read(1)
  1089.                 
  1090.                 if tstatus != 'u':
  1091.                     return 1
  1092.                 
  1093.                 spos = h[-8:]
  1094.                 srcpos = u64(spos)
  1095.             return 1
  1096.         finally:
  1097.             self._lock_release()
  1098.  
  1099.  
  1100.     
  1101.     def versions(self, max = None):
  1102.         r = []
  1103.         a = r.append
  1104.         keys = self._vindex.keys()
  1105.         if max is not None:
  1106.             keys = keys[:max]
  1107.         
  1108.         for version in keys:
  1109.             if self.versionEmpty(version):
  1110.                 continue
  1111.             
  1112.             a(version)
  1113.             if max and len(r) >= max:
  1114.                 return r
  1115.                 continue
  1116.         
  1117.         return r
  1118.  
  1119.     
  1120.     def history(self, oid, version = None, size = 1, filter = None):
  1121.         self._lock_acquire()
  1122.         
  1123.         try:
  1124.             r = []
  1125.             pos = self._lookup_pos(oid)
  1126.             wantver = version
  1127.             while len(r) >= size:
  1128.                 return r
  1129.             h = self._read_data_header(pos)
  1130.             if h.version:
  1131.                 if wantver is not None and h.version != wantver:
  1132.                     if h.prev:
  1133.                         pos = h.prev
  1134.                         continue
  1135.                     else:
  1136.                         return r
  1137.                 
  1138.             else:
  1139.                 version = ''
  1140.                 wantver = None
  1141.             th = self._read_txn_header(h.tloc)
  1142.             if th.ext:
  1143.                 d = loads(th.ext)
  1144.             else:
  1145.                 d = { }
  1146.             d.update({
  1147.                 'time': TimeStamp(h.tid).timeTime(),
  1148.                 'user_name': th.user,
  1149.                 'description': th.descr,
  1150.                 'tid': h.tid,
  1151.                 'version': h.version,
  1152.                 'size': h.plen })
  1153.             if filter is None or filter(d):
  1154.                 r.append(d)
  1155.             
  1156.             if h.prev:
  1157.                 pos = h.prev
  1158.                 continue
  1159.             return r
  1160.         finally:
  1161.             self._lock_release()
  1162.  
  1163.  
  1164.     
  1165.     def _redundant_pack(self, file, pos):
  1166.         if not pos > 8:
  1167.             raise AssertionError, pos
  1168.         file.seek(pos - 8)
  1169.         p = u64(file.read(8))
  1170.         file.seek((pos - p) + 8)
  1171.         return file.read(1) not in ' u'
  1172.  
  1173.     
  1174.     def pack(self, t, referencesf):
  1175.         '''Copy data from the current database file to a packed file
  1176.  
  1177.         Non-current records from transactions with time-stamp strings less
  1178.         than packtss are ommitted. As are all undone records.
  1179.  
  1180.         Also, data back pointers that point before packtss are resolved and
  1181.         the associated data are copied, since the old records are not copied.
  1182.         '''
  1183.         if self._is_read_only:
  1184.             raise POSException.ReadOnlyError()
  1185.         
  1186.         stop = `TimeStamp(*time.gmtime(t)[:5] + (t % 60,))`
  1187.         if stop == z64:
  1188.             raise FileStorageError('Invalid pack time')
  1189.         
  1190.         if not self._index:
  1191.             return None
  1192.         
  1193.         self._lock_acquire()
  1194.         
  1195.         try:
  1196.             if self._pack_is_in_progress:
  1197.                 raise FileStorageError('Already packing')
  1198.             
  1199.             self._pack_is_in_progress = True
  1200.             current_size = self.getSize()
  1201.         finally:
  1202.             self._lock_release()
  1203.  
  1204.         p = FileStoragePacker(self._file_name, stop, self._lock_acquire, self._lock_release, self._commit_lock_acquire, self._commit_lock_release, current_size)
  1205.         
  1206.         try:
  1207.             opos = None
  1208.             
  1209.             try:
  1210.                 opos = p.pack()
  1211.             except RedundantPackWarning:
  1212.                 detail = None
  1213.                 logger.info(str(detail))
  1214.  
  1215.             if opos is None:
  1216.                 return None
  1217.             
  1218.             oldpath = self._file_name + '.old'
  1219.             self._lock_acquire()
  1220.             
  1221.             try:
  1222.                 self._file.close()
  1223.                 
  1224.                 try:
  1225.                     if os.path.exists(oldpath):
  1226.                         os.remove(oldpath)
  1227.                     
  1228.                     os.rename(self._file_name, oldpath)
  1229.                 except Exception:
  1230.                     self._file = open(self._file_name, 'r+b')
  1231.                     raise 
  1232.  
  1233.                 os.rename(self._file_name + '.pack', self._file_name)
  1234.                 self._file = open(self._file_name, 'r+b')
  1235.                 self._initIndex(p.index, p.vindex, p.tindex, p.tvindex, p.oid2tid, p.toid2tid, p.toid2tid_delete)
  1236.                 self._pos = opos
  1237.                 self._save_index()
  1238.             finally:
  1239.                 self._lock_release()
  1240.  
  1241.         finally:
  1242.             if p.locked:
  1243.                 self._commit_lock_release()
  1244.             
  1245.             self._lock_acquire()
  1246.             self._pack_is_in_progress = False
  1247.             self._lock_release()
  1248.  
  1249.  
  1250.     
  1251.     def iterator(self, start = None, stop = None):
  1252.         return FileIterator(self._file_name, start, stop)
  1253.  
  1254.     
  1255.     def lastTransaction(self):
  1256.         '''Return transaction id for last committed transaction'''
  1257.         return self._ltid
  1258.  
  1259.     
  1260.     def lastTid(self, oid):
  1261.         '''Return last serialno committed for object oid.
  1262.  
  1263.         If there is no serialno for this oid -- which can only occur
  1264.         if it is a new object -- return None.
  1265.         '''
  1266.         
  1267.         try:
  1268.             return self.getTid(oid)
  1269.         except KeyError:
  1270.             return None
  1271.  
  1272.  
  1273.     
  1274.     def cleanup(self):
  1275.         '''Remove all files created by this storage.'''
  1276.         for ext in ('', '.old', '.tmp', '.lock', '.index', '.pack'):
  1277.             
  1278.             try:
  1279.                 os.remove(self._file_name + ext)
  1280.             continue
  1281.             except OSError:
  1282.                 e = None
  1283.                 if e.errno != errno.ENOENT:
  1284.                     raise 
  1285.                 
  1286.                 e.errno != errno.ENOENT
  1287.             
  1288.  
  1289.         
  1290.  
  1291.     
  1292.     def record_iternext(self, next = None):
  1293.         index = self._index
  1294.         oid = index.minKey(next)
  1295.         (oid_as_long,) = unpack('>Q', oid)
  1296.         next_oid = pack('>Q', oid_as_long + 1)
  1297.         
  1298.         try:
  1299.             next_oid = index.minKey(next_oid)
  1300.         except ValueError:
  1301.             next_oid = None
  1302.  
  1303.         (data, tid) = self.load(oid, '')
  1304.         return (oid, tid, data, next_oid)
  1305.  
  1306.  
  1307.  
  1308. def shift_transactions_forward(index, vindex, tindex, file, pos, opos):
  1309.     '''Copy transactions forward in the data file
  1310.  
  1311.     This might be done as part of a recovery effort
  1312.     '''
  1313.     seek = file.seek
  1314.     read = file.read
  1315.     write = file.write
  1316.     index_get = index.get
  1317.     vindex_get = vindex.get
  1318.     pv = z64
  1319.     p1 = opos
  1320.     p2 = pos
  1321.     offset = p2 - p1
  1322.     pnv = None
  1323.     while None:
  1324.         h = read(TRANS_HDR_LEN)
  1325.         if len(h) < TRANS_HDR_LEN:
  1326.             break
  1327.         
  1328.         (tid, stl, status, ul, dl, el) = unpack(TRANS_HDR, h)
  1329.         if status == 'c':
  1330.             break
  1331.         
  1332.         tl = u64(stl)
  1333.         tpos = pos
  1334.         tend = tpos + tl
  1335.         otpos = opos
  1336.         thl = ul + dl + el
  1337.         h2 = read(thl)
  1338.         if len(h2) != thl:
  1339.             raise PackError(opos)
  1340.         
  1341.         seek(opos)
  1342.         write(h)
  1343.         write(h2)
  1344.         thl = TRANS_HDR_LEN + thl
  1345.         pos = tpos + thl
  1346.         opos = otpos + thl
  1347.         while pos < tend:
  1348.             seek(pos)
  1349.             h = read(DATA_HDR_LEN)
  1350.             (oid, serial, sprev, stloc, vlen, splen) = unpack(DATA_HDR, h)
  1351.             plen = u64(splen)
  1352.             if not plen:
  1353.                 pass
  1354.             dlen = DATA_HDR_LEN + 8
  1355.             if vlen:
  1356.                 dlen = dlen + 16 + vlen
  1357.                 pnv = u64(read(8))
  1358.                 seek(8, 1)
  1359.                 version = read(vlen)
  1360.                 pv = p64(vindex_get(version, 0))
  1361.                 if status != 'u':
  1362.                     vindex[version] = opos
  1363.                 
  1364.             
  1365.             tindex[oid] = opos
  1366.             if plen:
  1367.                 p = read(plen)
  1368.             else:
  1369.                 p = read(8)
  1370.                 p = u64(p)
  1371.                 if p >= p2:
  1372.                     p = p - offset
  1373.                 elif p >= p1:
  1374.                     p = index_get(oid, 0)
  1375.                 
  1376.                 p = p64(p)
  1377.             seek(opos)
  1378.             sprev = p64(index_get(oid, 0))
  1379.             write(pack(DATA_HDR, oid, serial, sprev, p64(otpos), vlen, splen))
  1380.             if vlen:
  1381.                 if not pnv:
  1382.                     write(z64)
  1383.                 elif pnv >= p2:
  1384.                     pnv = pnv - offset
  1385.                 elif pnv >= p1:
  1386.                     pnv = index_get(oid, 0)
  1387.                 
  1388.                 write(p64(pnv))
  1389.                 write(pv)
  1390.                 write(version)
  1391.             
  1392.             write(p)
  1393.             opos = opos + dlen
  1394.             pos = pos + dlen
  1395.         pos = pos + 8
  1396.         if status != 'u':
  1397.             index.update(tindex)
  1398.         
  1399.         tindex.clear()
  1400.         write(stl)
  1401.         opos = opos + 8
  1402.     return opos
  1403.  
  1404.  
  1405. def search_back(file, pos):
  1406.     seek = file.seek
  1407.     read = file.read
  1408.     seek(0, 2)
  1409.     s = p = file.tell()
  1410.     while p > pos:
  1411.         seek(p - 8)
  1412.         l = u64(read(8))
  1413.         if l <= 0:
  1414.             break
  1415.         
  1416.         p = p - l - 8
  1417.     return (p, s)
  1418.  
  1419.  
  1420. def recover(file_name):
  1421.     file = open(file_name, 'r+b')
  1422.     index = { }
  1423.     vindex = { }
  1424.     tindex = { }
  1425.     (pos, oid, tid) = read_index(file, file_name, index, vindex, tindex, recover = 1)
  1426.     if oid is not None:
  1427.         print 'Nothing to recover'
  1428.         return None
  1429.     
  1430.     opos = pos
  1431.     (pos, sz) = search_back(file, pos)
  1432.     if pos < sz:
  1433.         npos = shift_transactions_forward(index, vindex, tindex, file, pos, opos)
  1434.     
  1435.     file.truncate(npos)
  1436.     print 'Recovered file, lost %s, ended up with %s bytes' % (pos - opos, npos)
  1437.  
  1438.  
  1439. def read_index(file, name, index, vindex, tindex, stop = '\xff' * 8, ltid = z64, start = 0x4L, maxoid = z64, recover = 0, read_only = 0):
  1440.     """Scan the file storage and update the index.
  1441.  
  1442.     Returns file position, max oid, and last transaction id.  It also
  1443.     stores index information in the three dictionary arguments.
  1444.  
  1445.     Arguments:
  1446.     file -- a file object (the Data.fs)
  1447.     name -- the name of the file (presumably file.name)
  1448.     index -- fsIndex, oid -> data record file offset
  1449.     vindex -- dictionary, oid -> data record offset for version data
  1450.     tindex -- dictionary, oid -> data record offset
  1451.               tindex is cleared before return
  1452.  
  1453.     There are several default arguments that affect the scan or the
  1454.     return values.  TODO:  document them.
  1455.  
  1456.     start -- the file position at which to start scanning for oids added
  1457.              beyond the ones the passed-in indices know about.  The .index
  1458.              file caches the highest ._pos FileStorage knew about when the
  1459.              the .index file was last saved, and that's the intended value
  1460.              to pass in for start; accept the default (and pass empty
  1461.              indices) to recreate the index from scratch
  1462.     maxoid -- ignored (it meant something prior to ZODB 3.2.6; the argument
  1463.               still exists just so the signature of read_index() stayed the
  1464.               same)
  1465.  
  1466.     The file position returned is the position just after the last
  1467.     valid transaction record.  The oid returned is the maximum object
  1468.     id in `index`, or z64 if the index is empty.  The transaction id is the
  1469.     tid of the last transaction, or ltid if the index is empty.
  1470.     """
  1471.     read = file.read
  1472.     seek = file.seek
  1473.     seek(0, 2)
  1474.     file_size = file.tell()
  1475.     fmt = TempFormatter(file)
  1476.     if file_size:
  1477.         if file_size < start:
  1478.             raise FileStorageFormatError(file.name)
  1479.         
  1480.         seek(0)
  1481.         if read(4) != packed_version:
  1482.             raise FileStorageFormatError(name)
  1483.         
  1484.     elif not read_only:
  1485.         file.write(packed_version)
  1486.     
  1487.     return (0x4L, z64, ltid)
  1488.     index_get = index.get
  1489.     pos = start
  1490.     seek(start)
  1491.     tid = '\x00' * 7 + '\x01'
  1492.     while None:
  1493.         h = read(TRANS_HDR_LEN)
  1494.         if not h:
  1495.             break
  1496.         
  1497.         if len(h) != TRANS_HDR_LEN:
  1498.             if not read_only:
  1499.                 logger.warning('%s truncated at %s', name, pos)
  1500.                 seek(pos)
  1501.                 file.truncate()
  1502.             
  1503.             break
  1504.         
  1505.         (tid, tl, status, ul, dl, el) = unpack(TRANS_HDR, h)
  1506.         if tid <= ltid:
  1507.             logger.warning('%s time-stamp reduction at %s', name, pos)
  1508.         
  1509.         ltid = tid
  1510.         if pos + tl + 8 > file_size or status == 'c':
  1511.             if not read_only:
  1512.                 logger.warning('%s truncated, possibly due to damaged records at %s', name, pos)
  1513.                 _truncate(file, name, pos)
  1514.             
  1515.             break
  1516.         
  1517.         if status not in ' up':
  1518.             logger.warning('%s has invalid status, %s, at %s', name, status, pos)
  1519.         
  1520.         if tl < TRANS_HDR_LEN + ul + dl + el:
  1521.             seek(-8, 2)
  1522.             rtl = u64(read(8))
  1523.             if file_size - rtl < pos or rtl < TRANS_HDR_LEN:
  1524.                 logger.critical('%s has invalid transaction header at %s', name, pos)
  1525.                 if not read_only:
  1526.                     logger.warning('It appears that there is invalid data at the end of the file, possibly due to a system crash.  %s truncated to recover from bad data at end.' % name)
  1527.                     _truncate(file, name, pos)
  1528.                 
  1529.                 break
  1530.             elif recover:
  1531.                 return (pos, None, None)
  1532.             
  1533.             panic('%s has invalid transaction header at %s', name, pos)
  1534.         
  1535.         if tid >= stop:
  1536.             break
  1537.         
  1538.         tpos = pos
  1539.         tend = tpos + tl
  1540.         if status == 'u':
  1541.             seek(tend)
  1542.             h = u64(read(8))
  1543.             if h != tl:
  1544.                 if recover:
  1545.                     return (tpos, None, None)
  1546.                 
  1547.                 panic('%s has inconsistent transaction length at %s', name, pos)
  1548.             
  1549.             pos = tend + 8
  1550.             continue
  1551.         
  1552.         pos = tpos + TRANS_HDR_LEN + ul + dl + el
  1553.         while pos < tend:
  1554.             h = fmt._read_data_header(pos)
  1555.             dlen = h.recordlen()
  1556.             tindex[h.oid] = pos
  1557.             if h.version:
  1558.                 vindex[h.version] = pos
  1559.             
  1560.             if pos + dlen > tend or h.tloc != tpos:
  1561.                 if recover:
  1562.                     return (tpos, None, None)
  1563.                 
  1564.                 panic('%s data record exceeds transaction record at %s', name, pos)
  1565.             
  1566.             if index_get(h.oid, 0) != h.prev:
  1567.                 if h.prev:
  1568.                     if recover:
  1569.                         return (tpos, None, None)
  1570.                     
  1571.                     logger.error('%s incorrect previous pointer at %s', name, pos)
  1572.                 else:
  1573.                     logger.warning('%s incorrect previous pointer at %s', name, pos)
  1574.             
  1575.             pos += dlen
  1576.         if pos != tend:
  1577.             if recover:
  1578.                 return (tpos, None, None)
  1579.             
  1580.             panic("%s data records don't add up at %s", name, tpos)
  1581.         
  1582.         h = u64(read(8))
  1583.         if h != tl:
  1584.             if recover:
  1585.                 return (tpos, None, None)
  1586.             
  1587.             panic('%s redundant transaction length check failed at %s', name, pos)
  1588.         
  1589.         pos += 8
  1590.         index.update(tindex)
  1591.         tindex.clear()
  1592.     
  1593.     try:
  1594.         maxoid = index.maxKey()
  1595.     except ValueError:
  1596.         maxoid == z64
  1597.  
  1598.     return (pos, maxoid, ltid)
  1599.  
  1600.  
  1601. def _truncate(file, name, pos):
  1602.     file.seek(0, 2)
  1603.     file_size = file.tell()
  1604.     
  1605.     try:
  1606.         i = 0
  1607.         while None:
  1608.             oname = '%s.tr%s' % (name, i)
  1609.             if os.path.exists(oname):
  1610.                 i += 1
  1611.                 continue
  1612.             logger.warning('Writing truncated data from %s to %s', name, oname)
  1613.             o = open(oname, 'wb')
  1614.             file.seek(pos)
  1615.             cp(file, o, file_size - pos)
  1616.             o.close()
  1617.             break
  1618.     except:
  1619.         logger.error("couldn't write truncated data for %s", name, exc_info = True)
  1620.         raise POSException.StorageSystemError("Couldn't save truncated data")
  1621.  
  1622.     file.seek(pos)
  1623.     file.truncate()
  1624.  
  1625.  
  1626. class Iterator:
  1627.     '''A General simple iterator that uses the Python for-loop index protocol
  1628.     '''
  1629.     __index = -1
  1630.     __current = None
  1631.     
  1632.     def __getitem__(self, i):
  1633.         _Iterator__index = self._Iterator__index
  1634.         while i > _Iterator__index:
  1635.             _Iterator__index = _Iterator__index + 1
  1636.             self._Iterator__current = self.next(_Iterator__index)
  1637.         self._Iterator__index = _Iterator__index
  1638.         return self._Iterator__current
  1639.  
  1640.  
  1641.  
  1642. class FileIterator(Iterator, FileStorageFormatter):
  1643.     '''Iterate over the transactions in a FileStorage file.
  1644.     '''
  1645.     _ltid = z64
  1646.     _file = None
  1647.     
  1648.     def __init__(self, file, start = None, stop = None):
  1649.         if isinstance(file, str):
  1650.             file = open(file, 'rb')
  1651.         
  1652.         self._file = file
  1653.         if file.read(4) != packed_version:
  1654.             raise FileStorageFormatError(file.name)
  1655.         
  1656.         file.seek(0, 2)
  1657.         self._file_size = file.tell()
  1658.         self._pos = 0x4L
  1659.         if not start is None and isinstance(start, str):
  1660.             raise AssertionError
  1661.         if not stop is None and isinstance(stop, str):
  1662.             raise AssertionError
  1663.         if start:
  1664.             self._skip_to_start(start)
  1665.         
  1666.         self._stop = stop
  1667.  
  1668.     
  1669.     def __len__(self):
  1670.         return 0
  1671.  
  1672.     
  1673.     def iterator(self):
  1674.         return self
  1675.  
  1676.     
  1677.     def close(self):
  1678.         file = self._file
  1679.         if file is not None:
  1680.             self._file = None
  1681.             file.close()
  1682.         
  1683.  
  1684.     
  1685.     def _skip_to_start(self, start):
  1686.         file = self._file
  1687.         read = file.read
  1688.         seek = file.seek
  1689.         while None:
  1690.             h = read(16)
  1691.             if len(h) < 16:
  1692.                 return None
  1693.             
  1694.             (tid, stl) = unpack('>8s8s', h)
  1695.             if tid >= start:
  1696.                 return None
  1697.             
  1698.             tl = u64(stl)
  1699.             
  1700.             try:
  1701.                 self._pos += tl + 8
  1702.             except OverflowError:
  1703.                 self._pos = long(self._pos) + tl + 8
  1704.  
  1705.             if __debug__:
  1706.                 seek(self._pos - 8, 0)
  1707.                 rtl = read(8)
  1708.                 if rtl != stl:
  1709.                     pos = file.tell() - 8
  1710.                     panic('%s has inconsistent transaction length at %s (%s != %s)', file.name, pos, u64(rtl), u64(stl))
  1711.                 
  1712.             rtl != stl
  1713.  
  1714.     
  1715.     def next(self, index = 0):
  1716.         if self._file is None:
  1717.             raise IOError('iterator is closed')
  1718.         
  1719.         pos = self._pos
  1720.         while None:
  1721.             
  1722.             try:
  1723.                 h = self._read_txn_header(pos)
  1724.             except CorruptedDataError:
  1725.                 err = None
  1726.                 if not err.buf:
  1727.                     break
  1728.                 
  1729.                 raise 
  1730.  
  1731.             if h.tid <= self._ltid:
  1732.                 logger.warning('%s time-stamp reduction at %s', self._file.name, pos)
  1733.             
  1734.             self._ltid = h.tid
  1735.             if self._stop is not None and h.tid > self._stop:
  1736.                 raise IndexError(index)
  1737.             
  1738.             if h.status == 'c':
  1739.                 raise IndexError(index)
  1740.             
  1741.             if pos + h.tlen + 8 > self._file_size:
  1742.                 logger.warning('%s truncated, possibly due to damaged records at %s', self._file.name, pos)
  1743.                 break
  1744.             
  1745.             if h.status not in ' up':
  1746.                 logger.warning('%s has invalid status, %s, at %s', self._file.name, h.status, pos)
  1747.             
  1748.             if h.tlen < h.headerlen():
  1749.                 self._file.seek(-8, 2)
  1750.                 rtl = u64(self._file.read(8))
  1751.                 if self._file_size - rtl < pos or rtl < TRANS_HDR_LEN:
  1752.                     logger.critical('%s has invalid transaction header at %s', self._file.name, pos)
  1753.                     logger.warning('It appears that there is invalid data at the end of the file, possibly due to a system crash.  %s truncated to recover from bad data at end.' % self._file.name)
  1754.                     break
  1755.                 else:
  1756.                     logger.warning('%s has invalid transaction header at %s', self._file.name, pos)
  1757.                     break
  1758.             
  1759.             tpos = pos
  1760.             tend = tpos + h.tlen
  1761.             if h.status != 'u':
  1762.                 pos = tpos + h.headerlen()
  1763.                 e = { }
  1764.                 if h.elen:
  1765.                     
  1766.                     try:
  1767.                         e = loads(h.ext)
  1768.  
  1769.                 
  1770.                 result = RecordIterator(h.tid, h.status, h.user, h.descr, e, pos, tend, self._file, tpos)
  1771.             
  1772.             rtl = u64(self._file.read(8))
  1773.             if rtl != h.tlen:
  1774.                 logger.warning('%s redundant transaction length check failed at %s', self._file.name, tend)
  1775.                 break
  1776.             
  1777.             self._pos = tend + 8
  1778.             return result
  1779.         raise IndexError(index)
  1780.  
  1781.  
  1782.  
  1783. class RecordIterator(Iterator, BaseStorage.TransactionRecord, FileStorageFormatter):
  1784.     '''Iterate over the transactions in a FileStorage file.'''
  1785.     
  1786.     def __init__(self, tid, status, user, desc, ext, pos, tend, file, tpos):
  1787.         self.tid = tid
  1788.         self.status = status
  1789.         self.user = user
  1790.         self.description = desc
  1791.         self._extension = ext
  1792.         self._pos = pos
  1793.         self._tend = tend
  1794.         self._file = file
  1795.         self._tpos = tpos
  1796.  
  1797.     
  1798.     def next(self, index = 0):
  1799.         pos = self._pos
  1800.         while pos < self._tend:
  1801.             h = self._read_data_header(pos)
  1802.             dlen = h.recordlen()
  1803.             if pos + dlen > self._tend or h.tloc != self._tpos:
  1804.                 logger.warning('%s data record exceeds transaction record at %s', file.name, pos)
  1805.                 break
  1806.             
  1807.             self._pos = pos + dlen
  1808.             prev_txn = None
  1809.             if h.plen:
  1810.                 data = self._file.read(h.plen)
  1811.             elif h.back == 0:
  1812.                 data = None
  1813.             else:
  1814.                 (data, tid) = self._loadBackTxn(h.oid, h.back, False)
  1815.                 prev_txn = self.getTxnFromData(h.oid, h.back)
  1816.             r = Record(h.oid, h.tid, h.version, data, prev_txn, pos)
  1817.             return r
  1818.         raise IndexError(index)
  1819.  
  1820.  
  1821.  
  1822. class Record(BaseStorage.DataRecord):
  1823.     '''An abstract database record.'''
  1824.     
  1825.     def __init__(self, oid, tid, version, data, prev, pos):
  1826.         self.oid = oid
  1827.         self.tid = tid
  1828.         self.version = version
  1829.         self.data = data
  1830.         self.data_txn = prev
  1831.         self.pos = pos
  1832.  
  1833.  
  1834.  
  1835. class UndoSearch:
  1836.     
  1837.     def __init__(self, file, pos, first, last, filter = None):
  1838.         self.file = file
  1839.         self.pos = pos
  1840.         self.first = first
  1841.         self.last = last
  1842.         self.filter = filter
  1843.         self.i = 0
  1844.         self.results = []
  1845.         self.stop = False
  1846.  
  1847.     
  1848.     def finished(self):
  1849.         '''Return True if UndoSearch has found enough records.'''
  1850.         if not self.i >= self.last and self.pos < 39:
  1851.             pass
  1852.         return self.stop
  1853.  
  1854.     
  1855.     def search(self):
  1856.         '''Search for another record.'''
  1857.         dict = self._readnext()
  1858.         if dict is not None:
  1859.             if self.filter is None or self.filter(dict):
  1860.                 if self.i >= self.first:
  1861.                     self.results.append(dict)
  1862.                 
  1863.                 self.i += 1
  1864.             
  1865.  
  1866.     
  1867.     def _readnext(self):
  1868.         '''Read the next record from the storage.'''
  1869.         self.file.seek(self.pos - 8)
  1870.         self.pos -= u64(self.file.read(8)) + 8
  1871.         self.file.seek(self.pos)
  1872.         h = self.file.read(TRANS_HDR_LEN)
  1873.         (tid, tl, status, ul, dl, el) = unpack(TRANS_HDR, h)
  1874.         if status == 'p':
  1875.             self.stop = 1
  1876.             return None
  1877.         
  1878.         if status != ' ':
  1879.             return None
  1880.         
  1881.         d = u = ''
  1882.         if ul:
  1883.             u = self.file.read(ul)
  1884.         
  1885.         if dl:
  1886.             d = self.file.read(dl)
  1887.         
  1888.         e = { }
  1889.         if el:
  1890.             
  1891.             try:
  1892.                 e = loads(self.file.read(el))
  1893.  
  1894.         
  1895.         d = {
  1896.             'id': base64.encodestring(tid).rstrip(),
  1897.             'time': TimeStamp(tid).timeTime(),
  1898.             'user_name': u,
  1899.             'size': tl,
  1900.             'description': d }
  1901.         d.update(e)
  1902.         return d
  1903.  
  1904.  
  1905.